﻿using SoundEngine3;
using SoundEngine3.AudioFile;
using SoundEngine3.AudioProcessing;
using SoundEngine3.AudioProcessing.Algorithms.SampleInterpolations;
using SoundEngine3.DspPlugins;
using SoundEngine3.DspPlugins.Meta;
using SoundEngine3.MIDI.Meta;
using System;
using System.Collections.Generic;
using System.IO;
using ZquenceStudio3.Core;
using ZquenceStudio3.Core.DspPlugins;
using ZquenceStudio3.DspPlugins;
using ZquenceStudio3.Core.Album;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers
{
    public class AudioSampleDescription : IDspPlugin
    {
        public DspPluginInfo GetDspPluginInfo()
        {
            return new DspPluginInfo("AudioSample", DspType.Synthesizer);
        }

        public BaseDspPluginInstance CreateDsp(ZquenceStudio3Context context, DspPluginInfo info)
        {
            return new AudioSample(context, info);
        }
    }
    
    public class AudioSample : CoreDspSynthesizerPlugin, IAudioSample
    {
        private string mAudioFilePath;
        private AudioBase mAudioFile;
        private List<MidiMessage> mMidiMessages;
        private List<OngoingSample> mOngoingSamples;
        private ContentFile mFileStream;
        private AudioNotifier mAudioNotifier;

        private static int UniqueIdNext;
    
        #region Parameters.

        /// <summary>
        /// Get or set the main volyme.
        /// </summary>
        public double Volyme { get; set; }

        public ContentFile FilePath
        {
            get { return mFileStream; }
            set
            {
                UnloadSample();

                if (value != null)
                {
                    LoadSample(value);
                }
            }
        }

        #endregion

        public bool IgnoreContentAttachement { get; set; }

        public override Version Version
        {
            get { return DspPluginInfo.DefaultVersion; }
        }

        public override string PluginInfo
        {
            get { return DspPluginInfo.DefaultPluginInfo; }
        }

        public AudioSample(ZquenceStudio3Context context, DspPluginInfo dspPlugin)
            : base(context, dspPlugin)
        {
            mAudioNotifier = new AudioNotifier(null, this);

            mMidiMessages = new List<MidiMessage>(2);
            mOngoingSamples = new List<OngoingSample>(32);
            
            // Create all parameters.
            CreateParameters();
            
            SupportAudioProcessing = true;

        }
        
        /// <summary>
        /// Create all parameters for this dsp.
        /// </summary>
        private void CreateParameters()
        {
            Volyme = 1.0f;
            
            // Volyme.
            ParameterPropertiesDirect volyme = new ParameterPropertiesDirect(this);
            volyme.Label = "Volyme";
            volyme.DirectGet += delegate() { return Volyme; };
            volyme.DirectSet += delegate(double value) { Volyme = value; };
            volyme.MinValue = 0;
            volyme.MaxValue = 2.0;
            AddParameter(volyme);

            // File path.
            ParameterPropertiesDirect filename = new ParameterPropertiesDirect(this);
            filename.Label = "Filename";
            filename.FileExtensions = "*.wav *.mp3 *.ogg";
            filename.Flags = ParameterPropertiesFlags.ParameterCanOpenStreamFile;
            filename.DirectGetObject += delegate()
            {
                return FilePath;
            };
            filename.DirectSetObject += delegate(object value)
            {
                FilePath = (ContentFile)value;
            };
            filename.OnValueChanged += delegate(ParameterProperties parameter) { parameter.Display = mAudioFilePath; };
            AddParameter(filename);
        }
        
        /// <summary>
        /// Load a supported audio file.
        /// </summary>
        /// <param name="audioFile">Content file.</param>
        public void LoadSample(ContentFile audioFile)
        {
            if (audioFile == null)
            {
                throw new ArgumentNullException(nameof(audioFile));
            }

            mAudioFilePath = audioFile.Filename;

            try
            {
                using (Stream stream = audioFile.GetStream())
                {
                    AudioFilePathBase audio = SoundEngineContext.CreateAudioSampleByStream(stream);
                    if (audio != null)
                    {
                        LoadSampleUsed(audio, audioFile);
                    }
                    else
                    {
                        throw new NotSupportedException("No supported audio file: " + audioFile.FilenameWithExtension);
                    }
                }
            }
            catch (Exception error)
            {
                AddError("Could not load '" + audioFile.FilenameWithExtension + "'");
            }
        }

        /// <summary>
        /// Return true if audio file is supported.
        /// </summary>
        /// <param name="path">Path to an audio file.</param>
        /// <returns>True if file is supported.</returns>
        public bool FileSupported(string path)
        {
            using (Stream stream = SharpDepend.Manager.FileManager.OpenFile(path))
            {
                if(SoundEngineContext.FileSupported(stream))
                {
                    return true;
                }
            }

            return false;
        }

        public void LoadSampleUsed(AudioBase audio, ContentFile contentFile)
        {
            if (audio == null)
            {
                throw new ArgumentNullException(nameof(audio));
            }
            else if (contentFile == null)
            {
                throw new ArgumentNullException(nameof(contentFile));
            }

            if (!this.IgnoreContentAttachement)
            {
                contentFile.AttachMember(this);
            }

            // Clear ongoing midis.
            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                mMidiMessages.Clear();
                mOngoingSamples.Clear();

                mFileStream = contentFile;
                mAudioFile = audio;
                mAudioNotifier.AudioFile = mAudioFile;
            }
            
            // Because this method is not called from a property change.
            mAudioNotifier.RaiseAudioFileChanged(0, (int)mAudioFile.Length);
        }

        public void UnloadSample()
        {
            // Clear ongoing midis.
            lock (SoundEngine3.SoundEngine.SoundLocker)
            {
                if (!this.IgnoreContentAttachement)
                {
                    if (mFileStream != null)
                    {
                        mFileStream.DetachMember(this);
                    }
                }

                mFileStream = null;
                mAudioFile = null;
                mAudioNotifier.AudioFile = null;
            }
        }
        
        public override void OnProcess(BaseAudioBuffer output)
        {
            var audioFile = mAudioFile;
            if (audioFile != null)
            {
                // Clear buffer with zero.
                output.Clear();

                if (mMidiMessages.Count > 0)
                {
                    for (int i = mMidiMessages.Count - 1; i >= 0; i--)
                    {
                        MidiMessage message = mMidiMessages[i];

                        OngoingSample ongoingSample = OngoingSample.Default;

                        ongoingSample.SetMidi(ref message);
                        ongoingSample.Volyme *= Volyme;
                        ongoingSample.UniqueId = ++UniqueIdNext;
                        ongoingSample.StartSample = message.DeltaFrame;
                        ongoingSample.Running = true;

                        int n = message.MidiData.Parameter1 - (12 * 4);

                        if (n == 0)
                        {
                            ongoingSample.LastPitch = ongoingSample.Pitch = 1.0;
                        }
                        else
                        {
                            ongoingSample.LastPitch = ongoingSample.Pitch = 1.0 * Math.Exp(0.69314718 * (n / 12.0));
                        }

                        mOngoingSamples.Add(ongoingSample);
                    }

                    mMidiMessages.Clear();
                }

                if (mOngoingSamples.Count > 0)
                {
                    for (int i = mOngoingSamples.Count - 1; i >= 0; i--)
                    {
                        OngoingSample sample = mOngoingSamples[i];

                        double repitch = sample.Pitch * SoundEngineContext.SampleRatePitch(audioFile.SourceSampleRate);
                        InterpolationState state;

                        double size = 0;

                        if (sample.ProcessedSamples == 0.0)
                        {
                            state = InterpolationState.Begin;
                            sample.LastVolyme = sample.Volyme;
                            sample.LastPan = sample.Pan;
                            sample.LastPitch = repitch;
                        }
                        else
                        {
                            state = InterpolationState.Continue;
                        }

                        int interpolationId = sample.UniqueId;
                        bool isAtEnd = sample.ProcessedSamples >= audioFile.Length;
                        if (isAtEnd)
                        {
                            state = InterpolationState.End;
                        }
                        
                        // If Mono.
                        if (output.Channels.Length == 1)
                        {
                            Channel mono = output.Channels[0];

                            size = audioFile.Process(mono, null, state, interpolationId, sample.ProcessedSamples, sample.StartSample, false
                                , sample.LastPitch, repitch
                                , sample.LastVolyme, sample.Volyme
                                , sample.LastPan, sample.Pan);
                        }
                        // If Stereo.
                        else if (output.Channels.Length > 1)
                        {
                            Channel left = output.Channels[0];
                            Channel right = output.Channels[1];

                            size = audioFile.Process(left, right, state, interpolationId, sample.ProcessedSamples, sample.StartSample, false
                                , sample.LastPitch, repitch
                                , sample.LastVolyme, sample.Volyme
                                , sample.LastPan, sample.Pan);
                        }

                        sample.StartSample = 0;
                        sample.ProcessedSamples += size;

                        // Check if we have reach the end.
                        if (sample.ProcessedSamples >= audioFile.Length || size == 0)
                        {
                            Channel left = output.Channels[0];
                            Channel right = output.Channels[1];
                            audioFile.Process(left, right, InterpolationState.End, interpolationId, sample.ProcessedSamples, sample.StartSample, false
                                , sample.LastPitch, repitch
                                , sample.LastVolyme, sample.Volyme
                                , sample.LastPan, sample.Pan);
                            mOngoingSamples.RemoveAt(i);
                            continue;
                        }

                        sample.LastPan = sample.Pan;
                        sample.LastVolyme = sample.Volyme;
                        sample.LastPitch = sample.Pitch;
                        mOngoingSamples[i] = sample;
                    }
                }
            }
        }

        public override void Dispose()
        {
            FilePath = null;
        }

        protected override void OnProcessEvents(List<MidiMessage> midiMessages)
        {
            foreach (var data in midiMessages)
            {
                // Only process note on status.
                if (data.MidiData.Status == MidiStatus.NOTE_ON)
                {
                    mMidiMessages.Add(data);
                }
            }
        }
        
        /// <summary>
        /// Return the native audio file this audio sampler owns.
        /// <para>The parameters is ignored.</para>
        /// <para>TODO: Gör om denna till en GetAudioNotifier(); som bara ska gälla för AudioSamples-synthar.</para>
        /// </summary>
        /// <param name="noteKey">Note key.</param>
        /// <param name="ppqLength">Ppq length.</param>
        /// <returns></returns>
        public override AudioNotifier GetAudioNotifier()
        {
            return mAudioNotifier;
        }

        /// <summary>
        /// Return name of audio file at C4 (Actual pitch of the audio).
        /// </summary>
        public override string GetNoteKeyLabel(int noteKey)
        {
            if (noteKey == 12 * 4)
            {
                return mFileStream.Filename;
            }
            else
            {
                return string.Empty;
            }
        }
    }
}
